#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include "include/types.h"
#include "include/libmod.h"
#include "include/modid.h"
#include "include/vmerr.h"
#include "include/nr_excp.h"
#include "mod_magicfs.h"

struct filenode {
    char name[MAX_NAMESZ];
    int fd;
    struct filenode * next;
};

struct filelist {
    char list[MAX_MFSFILE][MAX_NAMESZ];
    int sz;
};

struct mfs_ctx {
    struct {
        word mfsctl;
        word funct;
        word arg0;
        word arg1;
        word arg2;
        word arg3;
        word retv;
        word scratch;
    } csr;
    struct environ* env;
    int8* memory;
    struct {
        struct filenode head[MAX_MFSFILE];
    } filemap;
    struct {
        struct filelist fls;
    } filels;
    struct {
        pthread_t id;
        pthread_mutex_t ivklk;
        pthread_attr_t attr;
    } io;
};

status mfs_init (struct environ * env, struct module * mod);
status mfs_start (struct environ * env, struct module * mod);
status mfs_stop (struct environ * env, struct module * mod);
status mfs_pause (struct environ * env, struct module * mod);
status mfs_resume (struct environ * env, struct module * mod);
status mfs_ctrl (struct environ * env, struct module * mod, uint64 arg);
status mfs_csrrw (struct dev_csrrw_req req);

int
bkdr_hasher (char * str) {
    unsigned int seed = 131;
    unsigned int key = 0;
    while (*str) {
        key = key * seed + (*str++);
    }
    return (key % MAX_MFSFILE);
}

void
add_filenode (struct filenode * maphead, int idx, char * name, int fd) {
    struct filenode * newnode;
    idx %= MAX_MFSFILE;
    newnode = (struct filenode *) malloc (sizeof (struct filenode));
    strncpy (newnode->name, name, MAX_NAMESZ-1);
    newnode->fd = fd;
    newnode->next = maphead[idx].next;
    maphead[idx].next = newnode;
}

void
add_filelsitem (struct filelist* fls, char* name) {
    if (fls->sz < MAX_MFSFILE) {
        strcpy (fls->list[fls->sz], name);
        fls->sz++;
    }
}

int
getfd (struct filenode * maphead, char *name) {
    int idx = bkdr_hasher (name);
    idx %= MAX_MFSFILE;
    struct filenode * fptr;
    fptr = maphead[idx].next;
    while (fptr) {
        if (strcmp(fptr->name, name) == 0){
            return fptr->fd;
        }
        fptr = fptr->next;
    }
    return -1;
}

void
reload_file (struct mfs_ctx * ctx) {
    int i;
    bzero (&ctx->filels, sizeof (ctx->filels));
    struct filenode * fptr, *tptr;
    for (i = 0; i < MAX_MFSFILE; i++) {
        fptr = ctx->filemap.head[i].next;
        while (fptr) {
            (void) close (fptr->fd);
            tptr = fptr->next;
            free(fptr);
            fptr = tptr;
        }
    }
    bzero (&ctx->filemap, sizeof (ctx->filemap));
    DIR* dirp;
    struct dirent * diritm;
    int fd, idx, cnt, dfd;
    cnt = 0;
    if((dirp = opendir (MFS_HOME)) == NULL)
        return;
    dfd = dirfd (dirp);
    while ((diritm = readdir (dirp))) {
        if (cnt >= MAX_MFSFILE) break;
        if (diritm->d_type == DT_REG && 
                strlen (diritm->d_name) < MAX_NAMESZ) {
            idx = bkdr_hasher (diritm->d_name);
            fd = openat (dfd,diritm->d_name, O_RDWR);
            add_filenode (ctx->filemap.head, idx, diritm->d_name, fd);
            add_filelsitem (&ctx->filels.fls, diritm->d_name);
        }
        cnt++;
    }
    // for (i = 0; i < ctx->filels.fls.sz; i++) {
    //     printf ("%s -> %d\n", ctx->filels.fls.list[i], getfd (ctx->filemap.head,ctx->filels.fls.list[i]));
    // }
    closedir (dirp);
}


int
mfs_fetch (struct mfs_ctx * this, int8* lsaddr) {
    memcpy (lsaddr, &this->filels.fls.list, sizeof(this->filels.fls.list));
    return this->filels.fls.sz;
}

int
mfs_write (struct mfs_ctx * this, char *name,  unsigned int off, void * buf, unsigned int sz) {
    int fd, ret;
    fd = getfd (this->filemap.head, name);
    if (fd == -1) return -1;
    ret = pread (fd, buf, sz, off);
    return ret;
}

int
mfs_rm (struct mfs_ctx * this, char * path) {
    int fd, dfd;
    DIR* dirp = opendir (MFS_HOME);
    dfd = dirfd (dirp);
    fd = getfd (this->filemap.head, path);
    if (fd == -1) { 
        closedir (dirp);
        return -1;
    }
    close (fd);
    unlinkat (dfd, path, 0);
    closedir (dirp);
    reload_file (this);
    return 0;
}

int
mfs_read (struct mfs_ctx * this, int8 * path, unsigned int off, void * buf, unsigned int sz) {
    int fd, ret;
    fd = getfd (this->filemap.head, path);
    if (fd == -1) return -1;
    ret = pwrite (fd, buf, sz, off);
    return ret;
}


void *
io_thread (void * arg) {
    struct mfs_ctx * this = (struct mfs_ctx *) arg;
    reload_file (this);
    int retval;
    this->csr.mfsctl |= MFSCTL_DEVREADY;
    invoke:
    if (pthread_mutex_lock (&this->io.ivklk))
        return NULL;
    printf ("vmlog: mfs invoking.\n");
    switch(this->csr.funct) {
        case MFS_FETCH : {
            retval = mfs_fetch (this, this->memory + this->csr.arg0);
            break;
        }
        case MFS_READ : {
            retval = mfs_read (this,this->memory+this->csr.arg0, this->csr.arg1, this->memory+this->csr.arg2, this->csr.arg3);
            break;
        }
        case MFS_WRITE : {
            retval = mfs_write (this,this->memory+this->csr.arg0, this->csr.arg1, this->memory+this->csr.arg2, this->csr.arg3);
            break;
        }
        case MFS_RM : {
            retval = mfs_rm (this, this->memory + this->csr.arg0);
            break;
        }
        default :
        break;
    }
    this->csr.mfsctl &= ~MFSCTL_INVOKE;
    this->csr.retv = retval;
    raise_excep_async (this->env, INTRP_MFS_RET);
    printf ("vmlog: mfs finished.\n");
    goto invoke;
    return NULL;
}

status
mfs_assemble (struct module * mod) {
    status stat = STAT_SUCCESS;
    struct mfs_ctx * ctx = (struct mfs_ctx *)malloc (sizeof (struct mfs_ctx));
    bzero (ctx,sizeof (struct mfs_ctx));
    if (ctx == NULL) {
        stat |= ERR_NOMEM;
        goto error;
    }
    mod->context = ctx;
    mod->init = mfs_init;
    mod->start = mfs_start;
    mod->stop = mfs_stop;
    mod->pause = mfs_pause;
    mod->resume = mfs_resume;
    mod->modctl = mfs_ctrl;
    mod->stat = MOD_STAT_UNINITIALIZED;
    mod->modid = MFS_ID;
    strncpy (mod->name, "Biscuit-MagicFS", MAX_MOD_NAMESZ);
    stat |= module_add_item (mod, MI_DEV_CSRRW, mfs_csrrw);
    if (stat == STAT_SUCCESS)
        mod->magic = BISCUIT_MAGIC;
    return stat;
    error:
    return stat;
}

status
mfs_init (struct environ * env, struct module * mod) {
    status stat = STAT_SUCCESS;
    struct mfs_ctx * this = (struct mfs_ctx *) mod->context;
    if (mod->stat != MOD_STAT_UNINITIALIZED) {
        stat |= ERR_STATUS;
        goto error;
    }
    bzero (this, sizeof (struct mfs_ctx));
    mod->stat = MOD_STAT_STOPPED;
    if (pthread_mutex_init (&this->io.ivklk, NULL)) {
        stat |= ERR_THREAD;
        goto error;
    }
    if (pthread_mutex_lock (&this->io.ivklk)) {
        stat |= ERR_THREAD;
        goto error;
    }
    if (pthread_attr_init (&this->io.attr)) {
        stat |= ERR_THREAD;
        goto error;
    }
    this->memory = env->mem.data;
    this->env = env;
    mod->stat = MOD_STAT_STOPPED;
    return stat;
    error:
    return stat;
}

status
mfs_start (struct environ * env, struct module * mod) {
    (void) env;
    status stat = STAT_SUCCESS;
    struct mfs_ctx * this = (struct mfs_ctx *) mod->context;
    if (mod->stat != MOD_STAT_STOPPED) {
        stat |= ERR_STATUS;
        goto error;
    }
    if (pthread_create (&this->io.id, NULL, io_thread, this)) {
        stat |= ERR_THREAD;
        goto error;
    }
    mod->stat = MOD_STAT_RUNNING;
    return stat;
    error:
    return stat;
}

status
mfs_stop (struct environ * env, struct module * mod) {
    (void) env;
    status stat = STAT_SUCCESS;
    struct mfs_ctx * this = (struct mfs_ctx*) mod->context;
    if (mod->stat != MOD_STAT_RUNNING) {
        stat |= ERR_STATUS;
        goto error;
    }
    if (pthread_cancel (this->io.id)) {
        stat |= ERR_THREAD;
        goto error;
    }
    mod->stat = MOD_STAT_STOPPED;
    return stat;
    error:
    return stat;
}

status
mfs_pause (struct environ * env, struct module * mod) {
    (void) env;
    status stat = STAT_SUCCESS;
    struct mfs_ctx * this = (struct mfs_ctx *) mod->context;
    if (mod->stat != MOD_STAT_RUNNING) {
        stat |= ERR_STATUS;
        goto error;
    }
    if (pthread_cancel (this->io.id)) {
        stat |= ERR_THREAD;
        goto error;
    }
    mod->stat = MOD_STAT_PAUSED;
    return stat;
    error:
    return stat;
}

status
mfs_resume (struct environ * env, struct module * mod) {
    (void) env;
    status stat = STAT_SUCCESS;
    struct mfs_ctx * this = (struct mfs_ctx *) mod->context;
    if (mod->stat != MOD_STAT_PAUSED) {
        stat |= ERR_STATUS;
        goto error;
    }
    if (pthread_create (&this->io.id, NULL, io_thread, this)) {
        stat |= ERR_THREAD;
        goto error;
    }
    mod->stat = MOD_STAT_RUNNING;
    return stat;
    error:
    return stat;
}

status
mfs_ctrl (struct environ * env, struct module * mod, uint64 arg) {
    (void) env;
    (void) mod;
    (void) arg;
    return STAT_SUCCESS;
}

status
mfs_csrrw (struct dev_csrrw_req req) {
    status stat = STAT_SUCCESS;
    struct mfs_ctx * this = (struct mfs_ctx *) req.ctx;
    switch (req.addr) {
        case CSR_MFSARG0 : {
            if (IS_RMODE (req.rw)) {
                *req.recv = this->csr.arg0;
            }
            if (IS_WMODE (req.rw)) {
                this->csr.arg0 = req.send;
            }
            break;
        }
        case CSR_MFSARG1 : {
            if (IS_RMODE (req.rw)) {
                *req.recv = this->csr.arg1;
            }
            if (IS_WMODE (req.rw)) {
                this->csr.arg1 = req.send;
            }
            break;
        }
        case CSR_MFSARG2 : {
            if (IS_RMODE (req.rw)) {
                *req.recv = this->csr.arg2;
            }
            if (IS_WMODE (req.rw)) {
                this->csr.arg2 = req.send;
            }
            break;
        }
        case CSR_MFSARG3 : {
            if (IS_RMODE (req.rw)) {
                *req.recv = this->csr.arg3;
            }
            if (IS_WMODE (req.rw)) {
                this->csr.arg3 = req.send;
            }
            break;
        }
        case CSR_MFSCRATCH : {
            if (IS_RMODE (req.rw)) {
                *req.recv = this->csr.scratch;
            }
            if (IS_WMODE (req.rw)) {
                this->csr.scratch = req.send;
            }
            break;
        }
        case CSR_MFSFUNC : {
            if (IS_RMODE (req.rw)) {
                *req.recv = this->csr.funct;
            }
            if (IS_WMODE (req.rw)) {
                this->csr.funct = req.send;
            }
            break;
        }
        case CSR_MFSCTL : {
            if (IS_RMODE (req.rw)) {
                *req.recv = this->csr.mfsctl;
            }
            if (IS_WMODE (req.rw)) {
                this->csr.mfsctl = req.send;
                if (this->csr.mfsctl & MFSCTL_INVOKE)
                    pthread_mutex_unlock (&this->io.ivklk);
            }
            break;
        }
        case CSR_MFSRETV : {
            if (IS_RMODE (req.rw)) {
                *req.recv = this->csr.retv;
            }
            if (IS_WMODE (req.rw)) {
                this->csr.retv = req.send;
            }
            break;
        }
        default:
            goto nop;
    }
    req.rstat->req_ack = true;
    nop:
    return stat;
} 